/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.bean.chart.iterfaces;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.*;
import javax.swing.JPanel;

/**
 *
 * @author Lukasz,sammael
 */
public abstract class AbstractChart extends JPanel implements AbstractChartModel,DataSubject,
        AbstractChartView, LegendDataProvider, Serializable{

    public static final String PARAM_NAME_MAXYVALUE = "Wartość Y";
    public static final String PARAM_NAME_COUNTVALUE = "Ilośc punktów na wykresie";
    public static final String PARAM_NAME_DOTSIZE = "Wielkośc punktora";
    public static final String PARAM_NAME_STROKESIZE = "Grubość lini";
    public static final String PARAM_NAME_RUNNING = "Pobieranie wartości";
    public static final String PARAM_NAME_LINEVISIBLE = "Widoczność linii";
    public static final String PARAM_NAME_DOTVISIBLE = "Widoczność punktów";
    public static final String PARAM_NAME_GRIDVISIBLE = "Widoczność siatki";
    public static final String PARAM_NAME_XVISIBLE = "Widoczność x";
    public static final String PARAM_NAME_YVISIBLE = "Widoczność y";
    public static final String PARAM_NAME_VALUEVISIBLE = "Widoczność wawrtości";
    public static final String PARAM_NAME_CHARTCOLOR = "Tablica kolorów";
    public static final String PARAM_NAME_CHARTCOLORINDEX = "Wartość koloru dla indeksu";
    public static final String PARAM_NAME_BCKGCOLOR = "Kolor tła";
    public static final String PARAM_NAME_GRIDCOLOR = "Kolor siatki";
    public static final String PARAM_NAME_DOTCOLOR = "Kolor punktów";
    public static final String PARAM_NAME_VALUECOLOR = "Kolor wartości";
    public static final String PARAM_NAME_STEPMARKSCOUNT = "Liczba zaznaczeń na osi X";
    public static final String PARAM_NAME_REFRESHRATE = "czas odswiezania wykresu";
    public static final String PARAM_NAME_READMODE = "tryb odczytywania danych";
    
    
    private double maxYValue = 10;
    private int countValue = 5;
    private int dotSize = 10;
    private int strokeSize = 1;
    private int markCount = 5;
    private long refreshRate = 1000;

    protected ConcurrentHashMap<String, List<Double>> chartMap = new ConcurrentHashMap<>();
    protected ConcurrentHashMap<String, Color> chartColorMap = new ConcurrentHashMap<>();

    private ChartReadMode mode = ChartReadMode.WAIT; 
    //private String unit = "J";
    //private String legend = "Legenda";
    
    private boolean running = true;
    private boolean lineVisible = true;
    private boolean dotVisible = true;
    private boolean gridVisible = true;
    private boolean xVisible = true;
    private boolean yVisible = true;
    private boolean valueVisible = true;
    
    private Color[] chartColors = new Color[0];
    private Color bckgColor = Color.WHITE;
    private Color gridColor = Color.decode("0xb9b9b9");
    private Color valueColor = Color.decode("0x2c2c2c");
    private ExecutorService executor = Executors.newCachedThreadPool();
    private ConcurrentHashMap<String,LinkedBlockingQueue<Double> >queues = new ConcurrentHashMap<>();
    private List<DataObserver> observerList = new ArrayList<>();
    private final Task waitTask = new Task() {

        @Override
        public void read() throws InterruptedException{
                Set q = queues.entrySet();
                Iterator i = q.iterator();
                while(i.hasNext()){
                    Map.Entry<String,LinkedBlockingQueue<Double> > entry = (Map.Entry<String,LinkedBlockingQueue<Double> >) i.next();
                    String key = entry.getKey();
                    Double value = entry.getValue().poll(10, TimeUnit.MILLISECONDS);
                    if(value == null){
                        if(isRunning()){
                        value = 0.0;
                        put(key,value);
                        }
                    }else{
                    put(key,value);
                    }
                }
        }
    };
    private final Task skipTask = new Task() {

        @Override
        public void read() throws InterruptedException{
           Set q = queues.entrySet();
                Iterator i = q.iterator();
                while(i.hasNext()){
                    Map.Entry<String,LinkedBlockingQueue<Double> > entry = (Map.Entry<String,LinkedBlockingQueue<Double> >) i.next();
                    String key = entry.getKey();
                    LinkedBlockingQueue<Double> cache = entry.getValue();
                    Double value = null;
                    ArrayList<Double> values= new ArrayList<>();
                    cache.drainTo(values);
                    if(!values.isEmpty()){
                    value = values.get(values.size()-1);
                    
                    }
                    if(value == null){
                        value = 0.0;
                    
                    }
                    put(key,value);
                }
        }
    };
    private final Task sumTask = new Task() {

        @Override
        public void read() throws InterruptedException{
            Set q = queues.entrySet();
                Iterator i = q.iterator();
                while(i.hasNext()){
                    Map.Entry<String,LinkedBlockingQueue<Double> > entry = (Map.Entry<String,LinkedBlockingQueue<Double> >) i.next();
                    String key = entry.getKey();
                    LinkedBlockingQueue<Double> cache = entry.getValue();
                    Double value = null;
                    Double sum = 0.0;
                    int size = cache.size();
                    while(!cache.isEmpty()){
                    value = entry.getValue().poll(10, TimeUnit.MILLISECONDS);
                    if(value == null){
                        value = 0.0;
                    }
                    sum += value;
                    }
                    sum = sum /size;
                    put(key,sum);
                }
        }
    };
    
    private final ConcurrentHashMap<ChartReadMode,Task> tasks = new ConcurrentHashMap<ChartReadMode,Task>(){
        {
           
            put(ChartReadMode.WAIT, waitTask);
            put(ChartReadMode.SKIP, skipTask);
            put(ChartReadMode.SUM,sumTask);
        }
    };

    public AbstractChart() {
        executor.execute(tasks.get(mode));
    }
    
    
    
    @Override
    public void addChartData(String chartName) {
        chartMap.put(chartName, new LinkedList<Double>());
        queues.put(chartName, new LinkedBlockingQueue<Double>());
        notifyObservers();
    }

    @Override
    public List<Double> getChartData(String chartName) {
        return chartMap.get(chartName);
    }

    @Override
    public void addChartDataPoint(String chartName, Double value) {
        
        if(getMaxYValue() < value)
            setMaxYValue(value);
        
        queues.get(chartName).offer(value);
        
    }

    @Override
    public void removeChartData(String chartName) {
        queues.remove(chartName);
        //chartColorMap.remove(chartName);???
        chartMap.remove(chartName);
        repaint();
    }

    @Override
    public void removeAllCharts() {
        queues.clear();
        chartColorMap.clear();
        chartMap.clear();
        repaint();
    }
    
    
    @Override
    public void setMaxYValue(double maxYValue) {
        double oldValue = this.maxYValue;
        this.maxYValue = maxYValue;
        repaint();
        //propertySupport.firePropertyChange(MAXYVALUE_PARAM_NAME, oldValue, maxYValue);
        propertyChange(PARAM_NAME_MAXYVALUE, oldValue, maxYValue);
    }

    @Override
    public double getMaxYValue() {
        return maxYValue;
    }

    @Override
    public void setRefreshRate(long milisec) {
        long old = this.refreshRate;
        this.refreshRate = milisec;
        propertyChange(PARAM_NAME_REFRESHRATE, old, milisec);
    }
    @Override
    public long getRefreshRate() {
        return refreshRate;
    }

    @Override
    public void setReadMode(ChartReadMode mode) {
        ChartReadMode old = this.mode;
        this.mode = mode;
        tasks.get(old);
        executor.execute(tasks.get(mode));
        propertyChange(PARAM_NAME_READMODE, old, mode);
    }
    
    @Override
    public ChartReadMode getReadMode() {
        return mode;
    }
    
    
    @Override
    public void setRunning(boolean running) {
        boolean oldRunning = this.running;
        this.running = running;
        if(oldRunning != running && !running){
            Collection<Task> taskset = tasks.values();
            for(Task t : taskset){
                t.stop();
            }
            try{
            waitTask.read();
            }catch(Exception e){
                
            }
        }else if(oldRunning != running && running){
            executor.execute(tasks.get(mode));
        }
        //propertySupport.firePropertyChange(RUNNING_PARAM_NAME, oldRunning, running);
        propertyChange(PARAM_NAME_RUNNING, oldRunning, running);
    }

    @Override
    public boolean isRunning() {
        return running;
    }

    @Override
    public void setLineVisible(boolean visible) {
        boolean oldLineVisible = this.lineVisible;
        lineVisible = visible;
        //propertySupport.firePropertyChange(LINEVISIBLE_PARAM_NAME, oldLineVisible, lineVisible);
        propertyChange(PARAM_NAME_LINEVISIBLE, oldLineVisible, lineVisible);
    }

    @Override
    public boolean isLineVisible() {
        return lineVisible;
    }

    @Override
    public void setDotVisible(boolean visible) {
        boolean oldDotVisible = this.dotVisible;
        dotVisible = visible;
        //propertySupport.firePropertyChange(DOTVISIBLE_PARAM_NAME, oldDotVisible, visible);
        propertyChange(PARAM_NAME_DOTVISIBLE, oldDotVisible, visible);
    }

    @Override
    public boolean isDotVisible() {
        return dotVisible;
    }

    @Override
    public void setGridVisible(boolean visible) {
        boolean oldGridVisible = this.gridVisible;
        gridVisible = visible;
        //propertySupport.firePropertyChange(GRIDVISIBLE_PARAM_NAME, oldGridVisible, visible);
        propertyChange(PARAM_NAME_GRIDVISIBLE, oldGridVisible, visible);
        
    }

    @Override
    public boolean isGridVisible() {
        return  gridVisible;
    }

    @Override
    public void setChartColor(String name, Color color) {
        chartColorMap.put(name, color);
    }

    @Override
    public Color getChartColor(String name) {
        return chartColorMap.get(name);
    }

    @Override
    public void setChartColors(Color[] color) {
        Color[] oldChartColor = this.chartColors;
        chartColors = color;
        //propertySupport.firePropertyChange(CHARTCOLOR_PARAM_NAME, oldChartColor, color);
        propertyChange(PARAM_NAME_CHARTCOLOR, oldChartColor, color);
    }

    @Override
    public Color[] getChartColors() {
        return chartColors;
    }

    @Override
    public void setChartColors(int i, Color color) {
        Color oldChartColorIndex = this.chartColors[i];
        chartColors[i] = color;
        //propertySupport.firePropertyChange(CHARTCOLORINDEX_PARAM_NAME, oldChartColorIndex, color);
        propertyChange(PARAM_NAME_CHARTCOLORINDEX, oldChartColorIndex, color);
    }

    @Override
    public Color getChartColors(int i) {
        return chartColors[i];
    }

    @Override
    public void setBackgroundColor(Color color) {
        Color oldBckgColor = this.bckgColor;
        bckgColor = color;
        //propertySupport.firePropertyChange(BCKGCOLOR_PARAM_NAME, oldBckgColor, color);
        propertyChange(PARAM_NAME_BCKGCOLOR, oldBckgColor, color);
    }

    @Override
    public Color getBackgroundColor() {
        return bckgColor;
    }

    @Override
    public void setGridColor(Color color) {
        Color oldGridColor = this.gridColor;
        gridColor = color;
        repaint();
        //propertySupport.firePropertyChange(GRIDCOLOR_PARAM_NAME, oldGridColor, color);
        propertyChange(PARAM_NAME_GRIDCOLOR, oldGridColor, color);
    }

    @Override
    public Color getGridColor() {
        return gridColor;
    }

    @Override
    public void setValuesCount(int count) {
        int oldCountValue = this.countValue;
        countValue = count;
        
        //propertySupport.firePropertyChange(COUNTVALUE_PARAM_NAME, oldCountValue, count);
        propertyChange(PARAM_NAME_COUNTVALUE, oldCountValue, count);
    }

    @Override
    public int getValuesCount() {
        return countValue;
    }

    @Override
    public void setDotSize(int size) {
        int oldDotSize = this.dotSize;
        dotSize = size;
        //propertySupport.firePropertyChange(DOTSIZE_PARAM_NAME, oldDotSize, size);
        propertyChange(PARAM_NAME_DOTSIZE, oldDotSize, size);
    }

    @Override
    public int getDotSize() {
        return dotSize;
    }
    @Override
    public void setMarksCount(int count){
        int oldCount = markCount;
        markCount = count;
        propertyChange(PARAM_NAME_STEPMARKSCOUNT, oldCount, markCount);
    }
    @Override
    public int getMarksCount(){
        return markCount;
    }
    @Override
    public void setValueVisible(boolean  visible){
         boolean oldValueVisible = this.valueVisible;
        valueVisible = visible;
        //propertySupport.firePropertyChange(GRIDVISIBLE_PARAM_NAME, oldGridVisible, visible);
        propertyChange(PARAM_NAME_VALUEVISIBLE, oldValueVisible, visible);
    }
    @Override
    public boolean isValueVisible()
    {
        return valueVisible;
    }
    @Override
    public void setValueColor(Color color){
        Color oldValueColor = this.valueColor;
        valueColor = color;
        repaint();
        propertyChange(PARAM_NAME_VALUECOLOR, oldValueColor, color);
    }
    @Override
    public Color getValueColor(){
        return valueColor;
    }
    
    @Override
    public void setStrokeSize(int size){
        int oldValue = this.strokeSize;
        this.strokeSize = size;
        repaint();
      
        propertyChange(PARAM_NAME_STROKESIZE, oldValue, size);
    }
    @Override
    public int getStrokeSize(){
        return strokeSize;
    }
    @Override
    public void registerObserver(DataObserver observer) {
        observerList.add(observer);
    }

    @Override
    public void removeObserver(DataObserver observer) {
        observerList.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for(DataObserver ob : observerList){
            ob.update();
        }
    }
    
    @Override
    public void setXVisible(boolean visible){
        boolean oldVisible = this.xVisible;
        this.xVisible = visible;
        repaint();
        propertyChange(PARAM_NAME_XVISIBLE, oldVisible, visible);
    }
    @Override
    public boolean isXVisible(){
        return xVisible;
    }
    
    @Override
    public void setYVisible(boolean visible){
        boolean oldVisible = this.yVisible;
        this.yVisible = visible;
        repaint();
        propertyChange(PARAM_NAME_YVISIBLE, oldVisible, visible);
    }
    @Override
    public boolean isYVisible(){
        return yVisible;
    }
    
    @Override
    public abstract void paintComponent(Graphics g);
    
    protected abstract void paintGrid(Graphics g, int w, int h);
    protected abstract void paintLine(Graphics2D g2, int w, int h);
    protected abstract void paintDot(Graphics2D g2, int w, int h);
    protected abstract void paintValue(Graphics g, int h);
    protected abstract void propertyChange(String property,int oldval, int newVal);
    protected abstract void propertyChange(String property,Object oldval, Object newVal);
    protected abstract void propertyChange(String property,double oldval, double newVal);
    protected abstract void propertyChange(String property,boolean oldval, boolean newVal);
    protected abstract class Task implements Runnable{
        private boolean stop = false;
        public void stop(){
            stop = true;
        }
        protected void put(String chart,Double value){
            LinkedList<Double> data = (LinkedList<Double>) chartMap.get(chart);
            data.addLast(value);
            if(countValue<data.size()){
                data.removeFirst();
            }
        }
        @Override
        public void run() {
            stop = false;
            while(!stop){
                try{
                    
                Thread.sleep(refreshRate);
                read();
                repaint();
                }catch(Exception e){
                    
                }
            }
        }
        public abstract void read() throws InterruptedException;
       
    }
}
